package com.mxgraph.view;

import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxPoint;

public class mxSpaceManager extends mxEventSource {

    /**
     * Defines the type of the source or target terminal. The type is a string
     * passed to mxCell.is to check if the rule applies to a cell.
     */
    protected mxGraph graph;

    /**
     * Optional string that specifies the value of the attribute to be passed
     * to mxCell.is to check if the rule applies to a cell.
     */
    protected boolean enabled;

    /**
     * Optional string that specifies the attributename to be passed to
     * mxCell.is to check if the rule applies to a cell.
     */
    protected boolean shiftRightwards;

    /**
     * Optional string that specifies the attributename to be passed to
     * mxCell.is to check if the rule applies to a cell.
     */
    protected boolean shiftDownwards;

    /**
     * Optional string that specifies the attributename to be passed to
     * mxCell.is to check if the rule applies to a cell.
     */
    protected boolean extendParents;

    /**
     *
     */
    protected mxIEventListener resizeHandler = new mxIEventListener() {
        public void invoke(Object source, mxEventObject evt) {
            if (isEnabled()) {
                cellsResized((Object[]) evt.getProperty("cells"));
            }
        }
    };

    /**
     *
     */
    public mxSpaceManager(mxGraph graph) {
        setGraph(graph);
    }

    /**
     *
     */
    public boolean isCellIgnored(Object cell) {
        return !getGraph().getModel().isVertex(cell);
    }

    /**
     *
     */
    public boolean isCellShiftable(Object cell) {
        return getGraph().getModel().isVertex(cell)
                && getGraph().isCellMovable(cell);
    }

    /**
     * @return the enabled
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * @param value the enabled to set
     */
    public void setEnabled(boolean value) {
        enabled = value;
    }

    /**
     * @return the shiftRightwards
     */
    public boolean isShiftRightwards() {
        return shiftRightwards;
    }

    /**
     * @param shiftRightwards the shiftRightwards to set
     */
    public void setShiftRightwards(boolean shiftRightwards) {
        this.shiftRightwards = shiftRightwards;
    }

    /**
     * @return the shiftDownwards
     */
    public boolean isShiftDownwards() {
        return shiftDownwards;
    }

    /**
     * @param shiftDownwards the shiftDownwards to set
     */
    public void setShiftDownwards(boolean shiftDownwards) {
        this.shiftDownwards = shiftDownwards;
    }

    /**
     * @return the extendParents
     */
    public boolean isExtendParents() {
        return extendParents;
    }

    /**
     * @param extendParents the extendParents to set
     */
    public void setExtendParents(boolean extendParents) {
        this.extendParents = extendParents;
    }

    /**
     * @return the graph
     */
    public mxGraph getGraph() {
        return graph;
    }

    /**
     * @param graph the graph to set
     */
    public void setGraph(mxGraph graph) {
        if (this.graph != null) {
            this.graph.removeListener(resizeHandler);
        }

        this.graph = graph;

        if (this.graph != null) {
            this.graph.addListener(mxEvent.RESIZE_CELLS, resizeHandler);
            this.graph.addListener(mxEvent.FOLD_CELLS, resizeHandler);
        }
    }

    /**
     *
     */
    protected void cellsResized(Object[] cells) {
        if (cells != null) {
            mxIGraphModel model = getGraph().getModel();

            model.beginUpdate();
            try {
                for (int i = 0; i < cells.length; i++) {
                    if (!isCellIgnored(cells[i])) {
                        cellResized(cells[i]);
                        break;
                    }
                }
            }
            finally {
                model.endUpdate();
            }
        }
    }

    /**
     *
     */
    protected void cellResized(Object cell) {
        mxGraph graph = getGraph();
        mxGraphView view = graph.getView();
        mxIGraphModel model = graph.getModel();

        mxCellState state = view.getState(cell);
        mxCellState pstate = view.getState(model.getParent(cell));

        if (state != null && pstate != null) {
            Object[] cells = getCellsToShift(state);
            mxGeometry geo = model.getGeometry(cell);

            if (cells != null && geo != null) {
                mxPoint tr = view.getTranslate();
                double scale = view.getScale();

                double x0 = state.getX() - pstate.getOrigin().getX()
                        - tr.getX() * scale;
                double y0 = state.getY() - pstate.getOrigin().getY()
                        - tr.getY() * scale;
                double right = state.getX() + state.getWidth();
                double bottom = state.getY() + state.getHeight();

                double dx = state.getWidth() - geo.getWidth() * scale + x0
                        - geo.getX() * scale;
                double dy = state.getHeight() - geo.getHeight() * scale + y0
                        - geo.getY() * scale;

                double fx = 1 - geo.getWidth() * scale / state.getWidth();
                double fy = 1 - geo.getHeight() * scale / state.getHeight();

                model.beginUpdate();
                try {
                    for (int i = 0; i < cells.length; i++) {
                        if (cells[i] != cell && isCellShiftable(cells[i])) {
                            shiftCell(cells[i], dx, dy, x0, y0, right, bottom,
                                    fx, fy, isExtendParents()
                                    && graph.isExtendParent(cells[i]));
                        }
                    }
                }
                finally {
                    model.endUpdate();
                }
            }
        }
    }

    /**
     *
     */
    protected void shiftCell(Object cell, double dx, double dy, double x0,
                             double y0, double right, double bottom, double fx, double fy,
                             boolean extendParent) {
        mxGraph graph = getGraph();
        mxCellState state = graph.getView().getState(cell);

        if (state != null) {
            mxIGraphModel model = graph.getModel();
            mxGeometry geo = model.getGeometry(cell);

            if (geo != null) {
                model.beginUpdate();
                try {
                    if (isShiftRightwards()) {
                        if (state.getX() >= right) {
                            geo = (mxGeometry) geo.clone();
                            geo.translate(-dx, 0);
                        } else {
                            double tmpDx = Math.max(0, state.getX() - x0);
                            geo = (mxGeometry) geo.clone();
                            geo.translate(-fx * tmpDx, 0);
                        }
                    }

                    if (isShiftDownwards()) {
                        if (state.getY() >= bottom) {
                            geo = (mxGeometry) geo.clone();
                            geo.translate(0, -dy);
                        } else {
                            double tmpDy = Math.max(0, state.getY() - y0);
                            geo = (mxGeometry) geo.clone();
                            geo.translate(0, -fy * tmpDy);
                        }

                        if (geo != model.getGeometry(cell)) {
                            model.setGeometry(cell, geo);

                            // Parent size might need to be updated if this
                            // is seen as part of the resize
                            if (extendParent) {
                                graph.extendParent(cell);
                            }
                        }
                    }
                }
                finally {
                    model.endUpdate();
                }
            }
        }
    }

    /**
     *
     */
    protected Object[] getCellsToShift(mxCellState state) {
        mxGraph graph = this.getGraph();
        Object parent = graph.getModel().getParent(state.getCell());
        boolean down = isShiftDownwards();
        boolean right = isShiftRightwards();

        return graph.getCellsBeyond(state.getX()
                + ((down) ? 0 : state.getWidth()), state.getY()
                + ((down && right) ? 0 : state.getHeight()), parent, right,
                down);
    }

    /**
     *
     */
    public void destroy() {
        setGraph(null);
    }

}
